package jdt.geom;

import java.util.ArrayList;
import java.util.List;

import jdt.utils.JdtUtil;

/**
 * Represents an edge.<br>
 * The points of this edge must be different
 * @author Shay
 *
 */
public class Edge {

	/* --- Data members --- */

	/** The points of this edge */

	private Point a;
	private Point b;

	/* --- Ctors. --- */
	
	/**
	 * Construct an edge using its points
	 * @param a one point of the edge
	 * @param b other point of the edge
	 * @throws GeometryException 
	 */
	public Edge(Point a, Point b) throws GeometryException {
		if(a.equals(b)){
			throw new GeometryException("Points must be different");
		} else {
			this.a = a;
			this.b = b;
		}
	}
	
	/* --- Public Methods --- */
	
	/**
	 * Intersect this edge with the other edge and return the intersection point.<br>
	 * If the edges don't intersect, return null.<br>
	 * This is an implementation of this algorithm: http://en.wikipedia.org/wiki/Line-line_intersection
	 * 
	 * @param other The other edge to intersect with
	 * @return the intersection point
	 */
	public Point intersect(Edge other){
		Point p1 = other.a;
		Point p2 = other.b;
		Point p3 = this.a;
		Point p4 = this.b;
		
		double denom = (p1.x - p2.x) * (p3.y - p4.y) - (p1.y - p2.y) * (p3.x - p4.x);
		if(Math.abs(denom) < JdtUtil.PERCISION){ // Parallel
			return null;	
		} else {
			double x = ((p1.x*p2.y - p1.y*p2.x)*(p3.x - p4.x) - (p1.x - p2.x)*(p3.x*p4.y - p3.y*p4.x))/denom;
			double y = ((p1.x*p2.y - p1.y*p2.x)*(p3.y - p4.y) - (p1.y - p2.y)*(p3.x*p4.y - p3.y*p4.x))/denom;
			Point intersectionPoint = new Point(x, y);

			if(isOn(intersectionPoint) && other.isOn(intersectionPoint)){
				return intersectionPoint;
			} else {
				return null;
			}
		}
	}
	
	/**
	 * Checks if the argument point is on this edge
	 * @param p The point to check
	 * @return true or false
	 */
	public boolean isOn(Point p){
		boolean on = true;
		
		double cp = (b.x - a.x) * (p.y - a.y) - (b.y - a.y) * (p.x - a.x);
		on = on && (cp == 0);
		
		double minX = Math.min(a.x, b.x);
		double maxX = Math.max(a.x, b.x);
		on = on && (p.x <= maxX && p.x >= minX);
		
		double minY = Math.min(a.getY(), b.getY());
		double maxY = Math.max(a.getY(), b.getY());
		on = on && (p.y <= maxY && p.y >= minY);
		
		return on;
	}
	
	/**
	 * @return the points of this edge
	 */
	public List<Point> getPoints(){
		List<Point> points = new ArrayList<Point>(2);
		points.add(a);
		points.add(b);
		return points;
	}
	
	@Override
	public String toString() {
		return "Edge " + a + " " + b;
	}
	
	/**
	 * Calculates the distance between the argument point and this edge
	 * @param p The point to calculate the distance for
	 * @return the distance
	 */
	public double distance(Point p){
		double ABx = b.x-a.x;
	    double ABy = b.y-a.y;
	    double num = ABx*(a.y-p.y)-ABy*(a.x-p.x);
	    if (num < 0) num = -num;
	    return num;
	}
	
	/* --- Equals and HashCode --- */

	/**
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result1 = prime + ((a == null) ? 0 : a.hashCode());
		int result2 = prime + ((b == null) ? 0 : b.hashCode());
		return result1 * result2;
	}

	/** 
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (!(obj instanceof Edge))
			return false;
		
		Edge other = (Edge) obj;
		return getPoints().containsAll(other.getPoints());
	}
}
